﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections;

namespace MoneyWentWhere.DataModel.Time
{
    public struct TimePeriod : IComparable
    {
        public readonly DateTime Start;
        public readonly TimePeriodUnit Unit;

        public TimePeriod(DateTime start, TimePeriodUnit unit)
        {
            Start = PeriodStart(start, unit);
            Unit = unit;
        }

        public TimeSpan Duration { get { return PeriodDuration(Start, Unit); } }

        public DateTime End { get { return Start + Duration; } }

        public TimePeriod NextPeriod { get { return new TimePeriod(End, Unit); } }

        public bool Contains(DateTime time)
        {
            return time >= Start && time < End;
        }

        public IEnumerable<TimePeriod> GetPeriodsUntil(DateTime end)
        {
            TimePeriod period = this;
            do
            {
                yield return period;
                period = period.NextPeriod;
            } 
            while (period.Start < end);
        }

        public static DateTime PeriodStart(DateTime time, TimePeriodUnit unit)
        {
            switch (unit)
            {
                case TimePeriodUnit.Day:
                    return time.Date;
                case TimePeriodUnit.Week:
                    // See when the week starts
                    DayOfWeek start = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
                    DayOfWeek now = time.DayOfWeek;
                    int diff = start - now;
                    if (diff > 0) { diff -= 7; }
                    return time.Date.AddDays(diff);
                case TimePeriodUnit.Month:
                    return new DateTime(time.Year, time.Month, 1);
                case TimePeriodUnit.QuarterYear:
                    if (time.Month <= 3) { return new DateTime(time.Year, 1, 1); }
                    if (time.Month <= 6) { return new DateTime(time.Year, 4, 1); }
                    if (time.Month <= 9) { return new DateTime(time.Year, 7, 1); }
                    return new DateTime(time.Year, 10, 1);
                case TimePeriodUnit.HalfYear:
                    if (time.Month <= 6) { return new DateTime(time.Year, 1, 1); }
                    return new DateTime(time.Year, 7, 1);
                case TimePeriodUnit.Year:
                    return new DateTime(time.Year, 1, 1);
                default:
                    throw new NotImplementedException("No impl for: " + unit);
            }
        }
        public static TimeSpan PeriodDuration(DateTime time, TimePeriodUnit unit)
        {
            DateTime start = PeriodStart(time, unit);
            switch (unit)
            {
                case TimePeriodUnit.Day:
                    return TimeSpan.FromDays(1);
                case TimePeriodUnit.Week:
                    return TimeSpan.FromDays(7);
                case TimePeriodUnit.Month:
                    return start.AddMonths(1) - start;
                case TimePeriodUnit.QuarterYear:
                    return start.AddMonths(3) - start;
                case TimePeriodUnit.HalfYear:
                    return start.AddMonths(6) - start;
                case TimePeriodUnit.Year:
                    return start.AddYears(1) - start;
                default:
                    throw new NotImplementedException("No impl for: " + unit);
            }
        }

        public override string ToString()
        {
            return Unit + " of " + Start.ToString("yyyy-mm-dd");
        }

        public override bool Equals(object obj)
        {
            if (!(obj is TimePeriod)) { return false; }
            TimePeriod that = (TimePeriod)obj;
            return this.Start.Equals(that.Start) && this.Unit.Equals(that.Unit);
        }

        public override int GetHashCode()
        {
            return Start.GetHashCode();
        }

        public int CompareTo(object obj)
        {
            if (!(obj is TimePeriod)) { return 0; }
            TimePeriod that = (TimePeriod)obj;

            int result = this.Start.CompareTo(that.Start);
            if (result != 0) { return result; }

            return this.Unit.CompareTo(that.Unit);
        }
    }
}
